package org.gicentre.apps.hide;

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.gicentre.treemappa.TreeMapNode;
import org.gicentre.data.Data;
import org.gicentre.data.summary.SummariseField;
import org.gicentre.data.summary.SummariseNode;
import org.gicentre.data.summary.SummariseNull;
import org.gicentre.tweet.Tweetable;
import org.gicentre.utils.colour.ColourTable;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PFont;
import processing.core.PGraphics;
import processing.core.PImage;
import sun.awt.windows.ThemeReader;

/** Draws a treemap according to a TreemapState object
 * 
 * @author Aidan Slingsby, giCentre
 *
 */
public class TreemapPanel implements Tweetable{

	PApplet applet; //reference to the main Processing sketch
	Rectangle bounds; //bounds of this panel
	PFont font;

	TreemapSummaryNode treemap; //treemap
	HashMap<Integer, TreemapSummaryNode> oldTreemapNodes=new HashMap<Integer, TreemapSummaryNode>();
	HashMap<Object, ColourTable> colours;
	HashMap<Object, ColourScaling> colourScalings;

	TreemapStateGui treemapStateGui; //treemap state
	private boolean localColourMin=false;
	boolean showLabels=true;
	private SummariseNode summaryNode;

	private PImage oldBuffer=null;
	protected List<PGraphics> buffers;

	boolean flagToDoStructuralRebuild=false;
	boolean flagToDoNonStructuralRebuild=false;
	boolean flagToRedraw=false;

	private Data data;

	Collection<SummariseField> summariseFields;
	SummariseNull summariseNull = new SummariseNull("null");

	float lerp=1; //current step in the morphing/fading process (between 0 (start) to 1 (finished)
	boolean transitionMorph=true; //whether transition is a morph or a fade
	static final float LERP_STEP_MORPH=0.05f; //tranistion step (value between 0 and 1)
	static final float LERP_STEP_FADE=0.1f; //tranistion step (value between 0 and 1)
	boolean morphTransitionsOn=true; //if true, transitions (morph/fade) turned on
	
	ThreadedDisplayer threadedDisplayer;
	int treemapW=0;
	int treemapH=0;
	
//	List<SavedState> historyStates=new ArrayList<SavedState>();
	SavedState currentSavedState=null;
	
	/**Constructor
	 * 
	 * @param applet the Processing sketch
	 * @param treemapState the treemap state
	 * @param font
	 * @param bounds
	 * @param data
	 * @param summariseFields //all the summarise variables
	 * @param colours HashMap of colourtables keyed to variables
	 * @param colourScalings hashmap of the colour scalings.
	 */
	public TreemapPanel(PApplet applet, TreemapStateGui treemapStateGui, PFont font, Rectangle bounds,Data data,Collection<SummariseField> summariseFields, HashMap<Object, ColourTable> colours,HashMap<Object, ColourScaling> colourScalings){
		this.applet=applet;
		this.bounds=bounds;
		this.treemapStateGui=treemapStateGui;
//		this.font=applet.createFont("Handwriting-Dakota", 12);
		this.font=applet.createFont("Tahoma", 12);
		this.summariseFields=summariseFields;
		this.data=data;
		this.colours=colours;
		this.colourScalings=colourScalings;

		//setup treemap properties
		treemapW=this.bounds.width;
		treemapH=this.bounds.height;

		PGraphics buffer=applet.createGraphics(bounds.width, bounds.height,PApplet.JAVA2D);
		buffer.beginDraw();
		buffer.background(255);
		buffer.smooth();
		buffer.endDraw();

		buffers=new ArrayList<PGraphics>();
		buffers.add(buffer);
	}

	/** Gets the image
	 * 
	 */
	public PImage getImageToTweet(){
		PGraphics graphics=applet.createGraphics(bounds.width, bounds.height, PApplet.JAVA2D);
		graphics.image(buffers.get(buffers.size()-1),0,0);
		PImage image = applet.createImage(bounds.width, bounds.height, PApplet.ARGB);
		image.copy(graphics,0,0,bounds.width,bounds.height,0,0,this.bounds.width,this.bounds.height);
		return image;
	}

	public void draw(){
		
		//check what's changed
		if (treemapStateGui.hierHasChanged()){
			flagToDoStructuralRebuild=true;
			flagToDoNonStructuralRebuild=true;
			flagToRedraw=true;
		}
		
		if (treemapStateGui.sizeHasChanged() || treemapStateGui.orderHasChanged() || treemapStateGui.layoutHasChanged()){
			flagToDoNonStructuralRebuild=true;
			flagToRedraw=true;
		}
		if (treemapStateGui.colourHasChanged()||treemapStateGui.appearanceHasChanged()){
			flagToRedraw=true;
		}

		if (flagToRedraw){
			oldBuffer=buffers.get(buffers.size()-1);
			if (threadedDisplayer!=null){
				threadedDisplayer.interrupt();
			}
			threadedDisplayer=new ThreadedDisplayer(
					flagToDoStructuralRebuild,
					flagToDoNonStructuralRebuild,
					(TreemapState)treemapStateGui.cloneState(),
					data,
					summariseFields,
					treemapW,
					treemapH,
					applet,
					colours,
					font,
					summaryNode,
					colourScalings,
					treemap,
					morphTransitionsOn,
//					(HashMap<Integer, TreemapSummaryNode>)oldTreemapNodes.clone(),
					localColourMin
				);
			threadedDisplayer.start();
		}

		if (threadedDisplayer!=null && threadedDisplayer.isReady()){
			this.buffers.clear();
			this.buffers.addAll(threadedDisplayer.buffers);
			threadedDisplayer.buffers.clear();
			this.summaryNode=threadedDisplayer.summaryNode;
			this.treemap=threadedDisplayer.treemap;
			this.oldTreemapNodes=threadedDisplayer.oldTreemapNodes;
			threadedDisplayer=null;
			lerp=0;
			
//			if (!treemapStateGui.getState().suppressHistoryAdd){
//				int index=historyStates.indexOf(currentSavedState);
//				if (index<historyStates.size()-1){
//					//remove any history more recent than this
//					for (int i=historyStates.size()-1;i>index;i--){
//						historyStates.remove(i);
////						System.out.println("Deleting at index "+i);
//					}
//				}
//				SavedState savedState=new SavedState(treemapStateGui.getState());
//				currentSavedState=savedState;
//				historyStates.add(savedState);
//			}
			applet.loop();
		}
		
		boolean morph=this.buffers.size()>1;
		
		if (!morph){
			applet.image(buffers.get(0),bounds.x,bounds.y);

			if (lerp<=1.5){
				if (oldBuffer!=null){
					//if transition is fade, draw an image of the previous state on top,
					//faded according to 'lerp'
					applet.tint(255,255,255,PApplet.lerp(255,0,lerp));
					applet.image(oldBuffer.get(),bounds.x,bounds.y);
					applet.noTint();
				}
				if (lerp<1.5+LERP_STEP_FADE){
					lerp+=LERP_STEP_FADE;
				}
			}
		}
		else{
			int bufferIndex=(int)PApplet.constrain(lerp*(1f/LERP_STEP_MORPH), 0, this.buffers.size()-1);
			applet.image(buffers.get(bufferIndex),bounds.x,bounds.y);
			if (lerp<=1.5+LERP_STEP_MORPH){
				lerp+=LERP_STEP_MORPH;
			}
		}

		
		
		//reset changestateflags
		treemapStateGui.resetChangeFlags();
		flagToDoNonStructuralRebuild=false;
		flagToDoStructuralRebuild=false;
		flagToRedraw=false;
	}
	
	
//	public void setStateHistoryBack(){
//		int index=historyStates.indexOf(this.currentSavedState)-1;
//		if (index>=0){
//			SavedState savedState=historyStates.get(index);
//			this.treemapStateGui.setState(savedState.treemapState,true);
//			this.currentSavedState=savedState;
//		}
//	}
//	
//	public void setStateHistoryForward(){
//		int index=historyStates.indexOf(this.currentSavedState)+1;
////		System.out.println("index="+index);
//		if (index<historyStates.size()){
//			SavedState savedState=historyStates.get(index);
//			this.treemapStateGui.setState(savedState.treemapState,true);
//			this.currentSavedState=savedState;
//		}
//	}

	
	public boolean isBusy(){
		return this.threadedDisplayer!=null && !this.threadedDisplayer.isReady();
	}

	/** get the root node as a SummariseNode
	 * 
	 * @return
	 */
	public SummariseNode getSummariseNode(){
		return this.summaryNode;
	}

	/** get the treemp root node
	 * 
	 * @return
	 */
	public TreemapSummaryNode getTreemapSummaryNode(){
		return this.treemap;
	}

	
	
	/**set whether the lightest colour is the local min
	 * 
	 * @param useLocalMin colouring, true if use local min; false if zero
	 */
	public void setUseLocalMin(boolean useLocalMin){
		localColourMin=useLocalMin;
		flagToDoNonStructuralRebuild();
	}

	/**get whether the lightest colour is local min
	 * 
	 * @return true if local min; false if zero
	 */
	public boolean getUseLocalMin(){
		return localColourMin;
	}

	
	/** Whether the shapes are in transition
	 * 
	 * @return
	 */
	public boolean isInTransition(){
		return lerp<=1.4;
	}




	/**Get the bounds of this panel
	 * 
	 * @return
	 */
	public Rectangle getBounds(){
		return this.bounds;
	}

	/**Check to see if transitions are on
	 * 
	 * @return onlyUseFadeTransitions
	 */
	public boolean isTransitionsOn(){
		return this.morphTransitionsOn;
	}

	/**Set transitions on/off (useful if morphing to too slow)
	 * 
	 * @param transitionsOn
	 */
	public void setTransitionsOn(boolean transitionsOn){
		this.morphTransitionsOn=transitionsOn;
	}


	/**Flag to do a structural rebuild (change in hierarchy - needs to resummarise all the data)
	 * 
	 */
	public void flagToDoStructuralRebuild(){
		flagToDoStructuralRebuild=true;
		flagToDoNonStructuralRebuild=true;
		flagToRedraw=true;
	}

	/**Flag to do a non-structural rebuild (change in geometry only)
	 * 
	 */
	public void flagToDoNonStructuralRebuild(){
		flagToDoNonStructuralRebuild=true;
		flagToRedraw=true;
	}

	/**Flag to do redraw
	 * 
	 */
	public void flagToRedraw(){
		flagToRedraw=true;
	}
	
	/**Change the bounds and treemap to fit
	 * 
	 * @param bounds
	 */
	public void setBounds(Rectangle bounds){
		this.bounds.setBounds(bounds);
		treemapW=bounds.width;
		treemapH=bounds.height;
		flagToDoNonStructuralRebuild();
	}
	
	/**Change the bounds, and the ratio of treemap
	 * 
	 * @param bounds
	 */
	public void setBounds(Rectangle bounds, float treemapWidthHeightRatio){
		this.bounds.setBounds(bounds);
		//calculate as if w should be preserved
		int w=bounds.width;
		int h=(int)(w/treemapWidthHeightRatio);
		//if h is too big, preserve h
		if (h>bounds.height){
			h=bounds.height;
			w=(int)(h*treemapWidthHeightRatio);
		}
		treemapW=w;
		treemapH=h;
		flagToDoNonStructuralRebuild();
	}


	/**Find the treemap node (at the leaf-level) that the mouse is in
	 * 
	 * @return
	 */
	public TreemapSummaryNode getNodeFromMouse(){
		TreemapSummaryNode foundNode=null;
		if (treemap!=null){
			Iterator<TreeMapNode> it = treemap.iterator();
			while (it.hasNext()){
				TreemapSummaryNode node = (TreemapSummaryNode)it.next();
				if (node!=null && node.getRectangle()!=null && node.getRectangle().contains(applet.mouseX-bounds.x,applet.mouseY-bounds.y)){
					foundNode=node;
				}
			}
		}
		//Return if it or any of its parents are dummy nodes (start with #)
		if (foundNode!=null){
			TreemapSummaryNode node2=foundNode;
			while (node2.getParent()!=null){
				if (node2.getLabel()!=null && node2.getLabel().startsWith("#")){
					return null;
				}
				node2=(TreemapSummaryNode)node2.getParent();
			}
		}

		return foundNode;
	}

	/** Cancelled rendering further frames and draws what it's done
	 * 
	 */
	public void cancelRendering(){
		if (threadedDisplayer!=null)
			threadedDisplayer.interrupt();
	}


}
